package com.alibaba.basic.service.impl;

import com.alibaba.basic.dto.LoginDto;
import com.alibaba.basic.exception.BusinessException;
import com.alibaba.basic.jwt.JwtUtils;
import com.alibaba.basic.jwt.RsaUtils;
import com.alibaba.basic.jwt.UserInfo;
import com.alibaba.basic.mapper.BaseMapper;
import com.alibaba.basic.query.BaseQuery;
import com.alibaba.basic.service.IBaseService;
import com.alibaba.basic.util.MD5Utils;
import com.alibaba.basic.util.PageList;
import com.alibaba.system.domain.Menu;
import com.alibaba.system.mapper.MenuMapper;
import com.alibaba.system.mapper.PermissionMapper;
import com.alibaba.user.domain.Logininfo;
import com.alibaba.user.domain.User;
import com.alibaba.user.mapper.LogininfoMapper;
import com.alibaba.user.mapper.UserMapper;
import jdk.nashorn.internal.runtime.options.LoggingOption;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.security.PrivateKey;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

// @Service 这个类不要交给spring管理，因为本类只用于让子类继承代码而已，不做真正的业务执行

@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public class BaseServiceImpl<T> implements IBaseService<T> {

    @Resource
    private LogininfoMapper logininfoMapper;
    @Autowired
    private BaseMapper<T> baseMapper;
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private UserMapper userMapper;
    @Resource
    private PermissionMapper permissionMapper;
    @Resource
    private MenuMapper menuMapper;
    @Value("${jwt.rsa.pri}")
    private String jwtRsaPrivate;

    @Transactional
    @Override
    public void add(T t) {
        baseMapper.save(t);
    }

    @Transactional
    @Override
    public void del(Long id) {
        baseMapper.remove(id);
    }

    @Transactional
    @Override
    public void update(T t) {
        baseMapper.update(t);
    }

    @Override
    public T findById(Long id) {
        return baseMapper.loadById(id);
    }

    @Override
    public List<T> findAll() {
        return baseMapper.loadAll();
    }

    @Override
    public PageList<T> queryPage(BaseQuery query) {
        //先查询总数量
        Long totals = baseMapper.queryCount(query);

        if(totals < 1){
            return new PageList<T>();
        }
        //再查询当前页的分页数据
        List<T> data = baseMapper.queryData(query);
        return new PageList<T>(totals,data);
    }

    @Transactional
    @Override
    public void patchDelete(Long[] ids) {
        baseMapper.patchDelete(ids);
    }

    @Override
    public Map<String, Object> account(LoginDto loginDto) {
        String username = loginDto.getUsername();
        String password = loginDto.getPassword();
        String type = loginDto.getType();
        // 参数非空校验
        if(StringUtils.isEmpty(username)||StringUtils.isEmpty(password)||StringUtils.isEmpty(type)){
            throw new BusinessException("相关参数不可为空!");
        }
        // 根据账号&type 查询用户,字段对应有三个username or phone  or email and type
        Logininfo logininfo = logininfoMapper.loadByUsernameAndType(username,type);
        // 判断账号是否存在 不存在抛出异常
        if(logininfo == null){
            throw new BusinessException("账号密码错误,请重新输入!");
        }
        // 判断密码是否一致 需要加盐加密比较
        String passWordTmp = MD5Utils.encrypByMd5(password + logininfo.getSalt());
        if (!logininfo.getPassword().equals(passWordTmp)){
            throw new BusinessException("账号密码错误,请重新输入!");
        }
        // redis 方案
//        // 生成uuid作为key
//        String token = UUID.randomUUID().toString();
//        // 将uuid&loginfor设置到redis中,设置30min有效期
//        redisTemplate.opsForValue().set(token, logininfo, 30, TimeUnit.MINUTES);
//        // 将 uuid中的敏感信息设置为空 作为value
//        logininfo.setSalt("");
//        logininfo.setPassword("");
//        // 将uuid&&logininfo 存入到map 中 返回给前端
//        Map<String, Object> map = new HashMap<>();
//        map.put("token", token);
//        map.put("logininfo",logininfo);
//        return map;
        //jwt方案
        return loginSuccessJwtHandler(logininfo);
    }

    /**
     * @Description: 用户登录成功后生成JWT加密串并放入到map对象中
     */
    public  HashMap<String, Object> loginSuccessJwtHandler(Logininfo logininfo){
        // 1.创建UserInfo对象，存储logininfo对象，并用于后续赋值
        UserInfo userInfo = new UserInfo();
        userInfo.setLogininfo(logininfo);
        // 2.判断是后台用户登录才需要设置菜单信息、权限信息，类型为0那么就是后台用户登录
        if(logininfo.getType() == 0){
            // 2.1.获取用户权限信息，我们只需要sn权限编码即可
            List<String> permissions = permissionMapper.loadOwnPermissionByLoginfoTmpId(logininfo.getId());
            // 2.2.获取用户菜单信息
            List<Menu> menus = menuMapper.loadOwnMenuByLogininId(logininfo.getId());
            // 2.3.将菜单信息，权限信息放入到userinfo对象中
            userInfo.setPermissions(permissions);
            userInfo.setMenus(menus);
        }
        // 3.根据密钥文件名称获取到密钥
        PrivateKey privateKey = RsaUtils.getPrivateKey(this.getClass().getClassLoader().getResource(jwtRsaPrivate).getFile());
        // 4.根据密钥、用户信息、获取JWT加密串
        String token = JwtUtils.generateTokenExpireInMinutes(userInfo, privateKey, 30000);
        // 5.按照正常逻辑我们直接返回token给前端即可，但是那样前端需要解密，我们使用简化版本JWT，还是返回用户信息
        // 封装token、用户信息、权限信息、菜单信息给前端
        HashMap<String, Object> map = new HashMap<>();
        map.put("token", token);
        map.put("logininfo", logininfo);
        map.put("permissions", userInfo.getPermissions());
        map.put("menus", userInfo.getMenus());
        return map;
    }

}
